本日將利用前一篇建立好的模型,結合先前的資料前處理,利用基礎的PyTorch語法,進行模型的訓練。
根據先前的文章,我們可以藉由以下語法,得到Pytorch的Data Loader:
# prepare dataset
df = pd.read_csv('data/dataset.csv')
datasets = {split : df[df['split'] == split].to_dict('records') for split in SPLITS}
transforms = preprocess.prepare_transform()
processed_datasets = {
split : monai.data.Dataset(data = datasets[split], transform = transforms)
for split in SPLITS
}
data_generators = {
split : torch.utils.data.DataLoader(processed_datasets[split],
batch_size = BATCH_SIZE,
shuffle = True,
collate_fn = monai.data.utils.pad_list_data_collate,
pin_memory=torch.cuda.is_available())
for split in SPLITS
}
但從前一篇當中,我們預計模型的輸出會是14維的一個向量,因此會需要把每個label全部Concat起來。對應到transforms
,就必須先做以下的修改,而當中的labels
就會是我們想要預測的14個症狀的Ground Truth:
LABEL_LIST = ['atelectasis', 'cardiomegaly', 'effusion', 'infiltration', 'mass', 'nodule', 'pneumonia', 'pneumothorax', 'consolidation', 'edema', 'emphysema', 'fibrosis', 'pleural', 'hernia']
transforms = [
monai.transforms.LoadImageD(keys = ['img']),
monai.transforms.EnsureChannelFirstD(keys = ['img']),
monai.transforms.ScaleIntensityD(keys = ['img']),
monai.transforms.ToTensorD(keys = ['img'] + LABEL_LIST), # 把label們也轉換成tensor
monai.transforms.AddChanneld(keys = LABEL_LIST), # 整理他們的維度
monai.transforms.ConcatItemsd(keys = LABEL_LIST, name = 'labels'), # 將整理後維度後的label進行疊合
]
接著進行抽樣就可以得到:
>>> for batch in data_generators['TEST']:
>>> break;
>>> batch['labels'].shape
(256, 14)
由於我們針對14個症狀,每一個類別都是二元分類問題,因此我這裡採用Torch中的BCEWithLogitsLoss作為Loss Function。
這裡做一下簡介,主要是由Binary Cross Entropy (基本上,其實也等同是Binomial Distribution的log-likelihood函數,不過差個負號跟常數倍)
與 Sigmoid Function (大多時候,在統計內也稱為Logistic Function)
所組成。
比起先對model的結果取Sigmoid再算BCE,放在一起的好處主要是有數值計算上的好處。(試想,分開等於先取exponetial再取log ...)另外,除了資料以外,損失函數幾乎是整個機械學習領域水最深的一塊,因此這邊就不再多做什麼贅述。
另外,optimizer的部份則選用常見於實驗中的Adam,好處是收斂快速,不過實務上有一些研究表示Generalization error比起其他慢的會差一些就是。
結合在一起也很容易,輸入以下即可:
loss_function = torch.nn.BCEWithLogitsLoss()
optimizer = torch.optim.Adam(model.parameters(), 1e-3)
建立好以上以後,就可以利用PyTorch透過反向傳播法(back-propogation)對模型進行訓練,參考語法如下:
inputs, labels = batch['img'].to(device), batch['labels'].float().to(device)
optimizer.zero_grad() # 初始化這次傳播的梯度
outputs = model(inputs) # 進行forward propogation
loss = loss_function(outputs, labels) # 計算當前的loss
loss.backward() # 利用loss計算梯度
optimizer.step() # 利用這次算出來的梯度,對模型的weights進行更新
一樣本次的實作有放在Github內,簡單執行就可以得到結果:
# python src/train.py
----------
epoch 1/1
1/515, train_loss: 1.8068
2/515, train_loss: 1.6626
3/515, train_loss: 1.5855
4/515, train_loss: 1.4236
5/515, train_loss: 1.4121
6/515, train_loss: 1.3223
7/515, train_loss: 1.2272
8/515, train_loss: 1.1468
9/515, train_loss: 1.0351
10/515, train_loss: 1.0378
簡單執行了10個step。很好,loss有在穩定下降,這代表模型是有在訓練集當中學習到東西的!
transforms
來符合模型的設計